/*
 * @copyright: Huang Ding
 * @Author: ding-cx
 * @Date: 2021-08-16 11:01:16
 * @LastEditors: ding
 * @LastEditTime: 2022-07-27 17:20:37
 * @Description: file content
 */

import * as egg from "egg";
import * as path from "path";
import * as fs from "fs";
import dineggControllerInitializer from "../controller/dineggControllerInitializer";
import { bindRouter } from "../../../decorator";
const globby = require("globby");

export default class DingAppWorkerLoader extends egg.AppWorkerLoader {
	//  加载模块中的service
	loadModuleService() {
		const importsModulesList: string[] = this.config?.modules?.imports || [];
		let match: string[];
		if (importsModulesList.length === 0) {
			match = process.env.EGG_TYPESCRIPT === "true" ? ["**/*.service.(js|ts)", "!**/*.d.ts"] : ["**/*.service.js"];
		} else {
			match = importsModulesList.map((item) => {
				return process.env.EGG_TYPESCRIPT === "true" ? `${item}/**/*.service.(js|ts)` : `${item}/**/*.service.js`;
			});
			process.env.EGG_TYPESCRIPT === "true" && match.push("!**/*.d.ts");
		}
		// 载入到 app.serviceClasses
		const opt = {
			call: true,
			// fieldClass: "mServiceClasses",
			override: true,
			property: "mServiceClasses",
			directory: [path.join(this.options.baseDir, "app/modules")],
			// 匹配某些文件，只加载这些
			match: match,
			// match: ["**/*.service.(js|ts)"],
			//忽略某些文件 不进行加载
			// ignore:[],
			// 过滤掉某些导出内容。这里已经加载到了文件。
			// filter(exports){},
			// 除了加载目录，下面的路径转为array： a/b/c.js => ['a', 'b', 'c']
			caseStyle(filePath: string) {
				// console.log(filePath);
				let fpath = filePath.substring(0, filePath.lastIndexOf(".")).split("/");
				fpath[fpath.length - 1] = fpath[fpath.length - 1].replace(/.service$/, ""); //处理最后一个的.service
				// console.log(fpath);
				const properties = fpath;
				const ret = properties.map((property) => {
					if (!/^[a-z][a-z0-9_-]*$/i.test(property)) {
						throw new Error(`${property} is not match 'a-z0-9_-' in ${filePath}`);
					}

					// use default camelize, will capitalize the first letter
					// foo_bar.js > FooBar
					// fooBar.js  > FooBar
					// FooBar.js  > FooBar
					// FooBar.js  > FooBar
					// FooBar.js  > fooBar (if lowercaseFirst is true)
					property = property.replace(/[_-][a-z]/gi, (s) => s.substring(1).toUpperCase());
					const first = property[0];
					return first.toLowerCase() + property.substring(1);
				});
				// console.log(ret);
				return ret;
			},
		};

		// module 上的service先挂在app上，然后合并到ctx.service中去
		this.loadToApp(opt.directory, "mServiceClasses", opt);
	}

	//  创建option 加载模块中的controller
	loadModuleController() {
		// @ts-ignore
		this.timing.start("Load Module Controller");

		const importsModulesList: string[] = this.config?.modules?.imports || [];
		let match: string[];
		if (importsModulesList.length === 0) {
			match = process.env.EGG_TYPESCRIPT === "true" ? ["**/*.controller.(js|ts)", "!**/*.d.ts"] : ["**/*.controller.js"];
		} else {
			match = importsModulesList.map((item) => {
				return process.env.EGG_TYPESCRIPT === "true" ? `${item}/**/*.controller.(js|ts)` : `${item}/**/*.controller.js`;
			});
			process.env.EGG_TYPESCRIPT === "true" && match.push("!**/*.d.ts");
		}
		// 载入到 app.serviceClasses
		const opt = {
			directory: [path.join(this.options.baseDir, "app/modules")],
			match: match,
			property: "controller_module",
			initializer: dineggControllerInitializer,
			// 除了加载目录，下面的路径转为array： a/b/c.js => ['a', 'b', 'c']
			caseStyle(filePath: string) {
				// console.log(filePath);
				let fpath = filePath.substring(0, filePath.lastIndexOf(".")).split("/");
				fpath[fpath.length - 1] = fpath[fpath.length - 1].replace(/.controller$/, ""); //处理最后一个的.service
				// console.log(fpath);
				const properties = fpath;
				const ret = properties.map((property) => {
					if (!/^[a-z][a-z0-9_-]*$/i.test(property)) {
						throw new Error(`${property} is not match 'a-z0-9_-' in ${filePath}`);
					}

					property = property.replace(/[_-][a-z]/gi, (s) => s.substring(1).toUpperCase());
					const first = property[0];
					return first.toLowerCase() + property.substring(1);
				});
				// console.log(ret);
				return ret;
			},
		};

		const controllerBase = opt.directory;
		// 模型的控制器绑定在moduleController属性上，后面再合并进controller
		this.loadToApp(controllerBase, "moduleController", opt);
		this.options.logger.info("[egg:loader] module Controller loaded: %s", controllerBase);
		// @ts-ignore
		this.timing.end("Load Module Controller");

		// return Object.assign(opt, outOption);
	}

	// 加载模块中的router
	loadModuleRouter() {
		const importsModulesList: string[] = this.config?.modules?.imports || [];
		let match: string[];
		if (importsModulesList.length === 0) {
			match = process.env.EGG_TYPESCRIPT === "true" ? ["**/router.(js|ts)", "!**/*.d.ts"] : ["**/router.js"];
		} else {
			match = importsModulesList.map((item) => {
				return process.env.EGG_TYPESCRIPT === "true" ? `${item}/**/router.(js|ts)` : `${item}/**/router.js`;
			});
			process.env.EGG_TYPESCRIPT === "true" && match.push("!**/*.d.ts");
		}

		const appModulesPath = path.join(this.options.baseDir, "app/modules");
		const filePaths = globby.sync(match, {
			cwd: appModulesPath,
		});
		for (const filePath of filePaths) {
			const fullPath = path.join(appModulesPath, filePath);
			if (!fs.statSync(fullPath).isFile()) continue;
			this.loadFile(fullPath); //加载每个模块的router.ts路由文件
		}
	}

	// 加载模块中的定时任务
	loadModuleScheduleTask() {
		const importsModulesList: string[] = this.config?.modules?.imports || [];
		let match: string[] = [];

		if (importsModulesList.length > 0) {
			// note 此处拼接的路径，提供的模块名必须前后均无/ 如：正确：modName/abc      错误：/mod/aaa/
			const match1 = importsModulesList.map((item) => {
				return "modules/" + item + "/**/task";
			});
			const match2 = importsModulesList.map((item) => {
				return "modules/" + item + "/**/schedule";
			});
			match = [...match1, ...match2];
		} else {
			match = ["modules/**/task", "modules/**/schedule"];
		}

		let taskFilesDirs = globby.sync(match, {
			cwd: path.join(this.options.baseDir, "app"),
			onlyDirectories: true,
			onlyFiles: false,
		});

		this.options.logger.info("[dinegg] loader loadModuleScheduleTask task list %j", taskFilesDirs);
		for (const filePath of taskFilesDirs) {
			const fullPath = path.join(this.options.baseDir, "app", filePath);
			if (!fs.statSync(fullPath).isDirectory()) continue;
			this.config?.schedule?.directory?.push?.(fullPath); //把文件full path挂载在临时属性。在app.js中使用。
		}
	}

	// 加载模块的app.js/agent.js启动自定义引导文件。与egg的app.js相同。它在加载时实例化，然后在框架的同名生命周期钩子中执行。
	loadAppBoot() {
		const importsModulesList: string[] = this.config?.modules?.imports || [];
		let match: string[];
		if (importsModulesList.length === 0) {
			match = process.env.EGG_TYPESCRIPT === "true" ? ["**/app.(js|ts)", "!**/*.d.ts"] : ["**/app.js"];
		} else {
			match = importsModulesList.map((item) => {
				return process.env.EGG_TYPESCRIPT === "true" ? `${item}/**/app.(js|ts)` : `${item}/**/app.js`;
			});
			process.env.EGG_TYPESCRIPT === "true" && match.push("!**/*.d.ts");
		}

		const filePaths = globby.sync(match, {
			cwd: path.join(this.options.baseDir, "app/modules"),
		});
		// console.log(filePaths);
		this.options.logger.info("[dinegg] load app.js boot list: %j", filePaths);

		(this.app as any).__moduleExportAppBootInstanceList = filePaths.map((item) => {
			const bootClass = this.loadFile(path.join(this.options.baseDir, "app/modules", item));
			// 在启动自定义钩子方面，必须定义为class
			return new bootClass(this.app);
		});
	}

	/**
	 * egg框架原始内部的方法。此处可覆盖自己的逻辑
	 * loadPlugin first, then loadConfig
	 * @since 1.0.0
	 */
	loadConfig() {
		this.loadPlugin();
		super.loadConfig();
	}

	/** 为支持装饰器绑定路由，扩展加载router的方法 */
	dineggLoadRouter(isEnableModule: boolean) {
		// app
		this.loadRouter(); // Dependent on controllers

		isEnableModule && this.loadModuleRouter(); //加载模块中的router
		bindRouter(this.app as any);
	}

	load() {
		const isEnableModule = !!this.config?.modules?.enable;
		const app: any = this.app;
		// 加载自定义app.js
		isEnableModule && this.loadAppBoot();

		// 涉及config挂载时机。因此需在此处挂载task。
		isEnableModule && this.loadModuleScheduleTask(); //挂载定时任务文件路径。在app.js中合并给config，然后egg-schedule可以拿到并执行调用。

		// app > plugin > core
		this.loadApplicationExtend();
		this.loadRequestExtend();
		this.loadResponseExtend();
		this.loadContextExtend();
		this.loadHelperExtend();

		this.loadCustomLoader();

		// app > plugin
		this.loadCustomApp();

		// app > plugin
		// dinegg框架 加载 module service
		isEnableModule && this.loadModuleService();
		this.loadService();
		//   把module中service合并到service对象上
		isEnableModule && Object.assign(app.serviceClasses, app.mServiceClasses);

		// app > plugin > core
		this.loadMiddleware();

		// dinegg框架加载自定义controller
		isEnableModule && this.loadModuleController();
		// 原egg controller加载
		this.loadController({ initializer: dineggControllerInitializer });
		// 如果启用了module，则合并controller，注意如果controller和module controller存在同名，controller会被覆盖
		isEnableModule && app.moduleController && Object.assign(app.controller, app.moduleController);

		this.dineggLoadRouter(isEnableModule);
	}
}
